// Copyright 2006 Bret Taylor
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.

#ifndef INDIE_AST_H__
#define INDIE_AST_H__

#include <assert.h>
#include <memory>
#include <string>
#include <vector>

#include "filelocation.h"
#include "memory.h"

using std::auto_ptr;
using std::string;
using std::vector;

namespace indie {

// A Visitor recursively traverses an AST with the Ast::Accept method. We
// pre-declare it here to avoid circular dependencies.
class Visitor;

// The binary operators supported by Indie.
typedef enum {
    Addition,
    Subtraction,
    Multiplication,
    Division,
    And,
    Or,
    Equal,
    NotEqual,
    LessThan,
    GreaterThan,
    LessEqual,
    GreaterEqual,
    Mod
} BinaryOperator;

// The unary operators supported by Indie.
typedef enum {
    Not,
    Negative,
    PreDecrement,
    PreIncrement,
    PostDecrement,
    PostIncrement
} UnaryOperator;

// Our base AST class. An instance of a subclass of this class represents
// a single node in our abstract syntax tree.
class Ast {
  public:
    virtual ~Ast();

    virtual void Accept(Visitor& visitor) const = 0;

    inline bool has_location() const {
	return (location_.get() != NULL);
    }

    inline const FileLocation& location() const {
	assert(has_location());
	return *location_;
    }

  protected:
    Ast(FileLocation* location);

  private:
    auto_ptr<FileLocation> location_;
};


class AstExpression : public Ast {
  public:
    virtual ~AstExpression();

  protected:
    AstExpression(FileLocation* location);
};


class AstStatement : public Ast {
  public:
    virtual ~AstStatement();

  protected:
    AstStatement(FileLocation* location);
};


class AstLValue : public AstExpression {
  public:
    virtual ~AstLValue();

  protected:
    AstLValue(FileLocation* location);
};


class AstType : public Ast {
  public:
    virtual ~AstType();

  protected:
    AstType(FileLocation* location);
};


class AstClassType : public AstType {
  public:
    AstClassType(FileLocation* location, string* identifier);
    virtual ~AstClassType();

    virtual void Accept(Visitor& visitor) const;

    inline const string& identifier() const {
        return *identifier_;
    }

  private:
    auto_ptr<string> identifier_;
};


class AstArrayType : public AstType {
  public:
    AstArrayType(FileLocation* location, AstType* element_type);
    virtual ~AstArrayType();

    virtual void Accept(Visitor& visitor) const;

    inline const AstType& element_type() const {
        return *element_type_;
    }

  private:
    auto_ptr<AstType> element_type_;
};


class AstUnionType : public Ast {
  public:
    AstUnionType(FileLocation* location, vector<AstType*>* types);
    virtual ~AstUnionType();

    virtual void Accept(Visitor& visitor) const;

    inline const vector<AstType*>& types() const {
        return *types_;
    }

  private:
    auto_collection_ptr<vector<AstType*> > types_;
};


class AstVariable : public Ast {
  public:
    AstVariable(FileLocation* location, string* identifier, AstUnionType* type);
    virtual ~AstVariable();

    virtual void Accept(Visitor& visitor) const;

    inline const string& identifier() const {
        return *identifier_;
    }

    inline const AstUnionType& type() const {
        return *type_;
    }

  private:
    auto_ptr<string> identifier_;
    auto_ptr<AstUnionType> type_;
};


class AstMethod : public Ast {
  public:
    AstMethod(FileLocation* location, string* identifier, 
              vector<AstVariable*>* parameters, AstUnionType* return_type, 
              AstStatement* body);
    virtual ~AstMethod();

    virtual void Accept(Visitor& visitor) const;

    inline const string& identifier() const {
        return *identifier_;
    }

    inline const vector<AstVariable*>& parameters() const {
        return *parameters_;
    }

    inline bool has_return_type() const {
        return return_type_.get() != NULL;
    }

    inline const AstUnionType& return_type() const {
        assert(has_return_type());
        return *return_type_;
    }

    inline const AstStatement& body() const {
        return *body_;
    }

  private:
    auto_ptr<string> identifier_;
    auto_collection_ptr<vector<AstVariable*> > parameters_;
    auto_ptr<AstUnionType> return_type_;
    auto_ptr<AstStatement> body_;
};


class AstClass : public Ast {
  public:
    AstClass(FileLocation* location, string* identifier, 
             AstClassType* superclass, vector<AstMethod*>* methods, 
             vector<AstVariable*>* variables);
    virtual ~AstClass();

    virtual void Accept(Visitor& visitor) const;

    inline const string& identifier() const {
        return *identifier_;
    }

    inline bool has_superclass() const {
        return superclass_.get() != NULL;
    }

    inline const AstClassType& superclass() const {
        assert(has_superclass());
        return *superclass_;
    }

    inline const vector<AstMethod*>& methods() const {
        return *methods_;
    }

    inline const vector<AstVariable*>& variables() const {
        return *variables_;
    }

  private:
    auto_ptr<string> identifier_;
    auto_ptr<AstClassType> superclass_;
    auto_collection_ptr<vector<AstMethod*> > methods_;
    auto_collection_ptr<vector<AstVariable*> > variables_;
};


class AstFile : public Ast {
  public:
    AstFile(FileLocation* location, vector<AstClass*>* classes);
    virtual ~AstFile();

    virtual void Accept(Visitor& visitor) const;

    inline const vector<AstClass*>& classes() const {
        return *classes_;
    }

  private:
    auto_collection_ptr<vector<AstClass*> > classes_;
};


class AstBlockStatement : public AstStatement {
  public:
    AstBlockStatement(FileLocation* location, 
                      vector<AstStatement*>* statements);
    virtual ~AstBlockStatement();

    virtual void Accept(Visitor& visitor) const;

    inline const vector<AstStatement*>& statements() const {
        return *statements_;
    }

  private:
    auto_collection_ptr<vector<AstStatement*> > statements_;
};


class AstExpressionStatement : public AstStatement {
  public:
    AstExpressionStatement(FileLocation* location, AstExpression* expression);
    virtual ~AstExpressionStatement();

    virtual void Accept(Visitor& visitor) const;

    inline const AstExpression& expression() const {
        return *expression_;
    }

  private:
    auto_ptr<AstExpression> expression_;
};


class AstAssignment : public AstStatement {
  public:
    AstAssignment(FileLocation* location, AstLValue* left_side, 
                  AstExpression* right_side);
    virtual ~AstAssignment();

    virtual void Accept(Visitor& visitor) const;

    inline const AstLValue& left_side() const {
        return *left_side_;
    }

    inline const AstExpression& right_side() const {
        return *right_side_;
    }

  private:
    auto_ptr<AstLValue> left_side_;
    auto_ptr<AstExpression> right_side_;
};


class AstVariableDeclaration : public AstStatement {
  public:
    AstVariableDeclaration(FileLocation* location, AstVariable* variable, 
                           AstExpression* initial_value);
    virtual ~AstVariableDeclaration();

    virtual void Accept(Visitor& visitor) const;

    inline const AstVariable& variable() const {
        return *variable_;
    }

    inline bool has_initial_value() const {
        return initial_value_.get() != NULL;
    }

    inline const AstExpression& initial_value() const {
        assert(has_initial_value());
        return *initial_value_;
    }

  private:
    auto_ptr<AstVariable> variable_;
    auto_ptr<AstExpression> initial_value_;
};


class AstEmptyStatement : public AstStatement {
  public:
    AstEmptyStatement(FileLocation* location);
    virtual ~AstEmptyStatement();

    virtual void Accept(Visitor& visitor) const;
};


class AstIf : public AstStatement {
  public:
    AstIf(FileLocation* location, AstExpression* test, AstStatement* true_path, 
          AstStatement* false_path);
    virtual ~AstIf();

    virtual void Accept(Visitor& visitor) const;

    inline const AstExpression& test() const {
        return *test_;
    }

    inline const AstStatement& true_path() const {
        return *true_path_;
    }

    inline bool has_false_path() const {
        return false_path_.get() != NULL;
    }

    inline const AstStatement& false_path() const {
        assert(has_false_path());
        return *false_path_;
    }

  private:
    auto_ptr<AstExpression> test_;
    auto_ptr<AstStatement> true_path_;
    auto_ptr<AstStatement> false_path_;
};


class AstWhile : public AstStatement {
  public:
    AstWhile(FileLocation* location, AstExpression* test, AstStatement* body);
    virtual ~AstWhile();

    virtual void Accept(Visitor& visitor) const;

    inline const AstExpression& test() const {
        return *test_;
    }

    inline const AstStatement& body() const {
        return *body_;
    }

  private:
    auto_ptr<AstExpression> test_;
    auto_ptr<AstStatement> body_;
};


class AstFor : public AstStatement {
  public:
    AstFor(FileLocation* location, AstStatement* initializer, 
           AstExpression* test, AstStatement* updater, AstStatement* body);
    virtual ~AstFor();

    virtual void Accept(Visitor& visitor) const;

    inline const AstStatement& initializer() const {
        return *initializer_;
    }

    inline const AstExpression& test() const {
        return *test_;
    }

    inline const AstStatement& updater() const {
        return *updater_;
    }

    inline const AstStatement& body() const {
        return *body_;
    }

  private:
    auto_ptr<AstStatement> initializer_;
    auto_ptr<AstExpression> test_;
    auto_ptr<AstStatement> updater_;
    auto_ptr<AstStatement> body_;
};


class AstBreak : public AstStatement {
  public:
    AstBreak(FileLocation* location);
    virtual ~AstBreak();

    virtual void Accept(Visitor& visitor) const;
};


class AstReturn : public AstStatement {
  public:
    AstReturn(FileLocation* location, AstExpression* expression);
    virtual ~AstReturn();

    virtual void Accept(Visitor& visitor) const;

    inline bool has_expression() const {
        return expression_.get() != NULL;
    }

    inline const AstExpression& expression() const {
        assert(has_expression());
        return *expression_;
    }

  private:
    auto_ptr<AstExpression> expression_;
};


class AstParenthesizedExpression : public AstExpression {
  public:
    AstParenthesizedExpression(FileLocation* location, 
                               AstExpression* expression);
    virtual ~AstParenthesizedExpression();

    virtual void Accept(Visitor& visitor) const;

    inline const AstExpression& expression() const {
        return *expression_;
    }

  private:
    auto_ptr<AstExpression> expression_;
};


class AstAccess : public AstLValue {
  public:
    AstAccess(FileLocation* location, AstExpression* base, string* identifier);
    virtual ~AstAccess();

    virtual void Accept(Visitor& visitor) const;

    inline bool has_base() const {
        return base_.get() != NULL;
    }

    inline const AstExpression& base() const {
        assert(has_base());
        return *base_;
    }

    inline const string& identifier() const {
        return *identifier_;
    }

  private:
    auto_ptr<AstExpression> base_;
    auto_ptr<string> identifier_;
};


class AstArrayAccess : public AstLValue {
  public:
    AstArrayAccess(FileLocation* location, AstExpression* base, 
                   AstExpression* index);
    virtual ~AstArrayAccess();

    virtual void Accept(Visitor& visitor) const;

    inline const AstExpression& base() const {
        return *base_;
    }

    inline const AstExpression& index() const {
        return *index_;
    }

  private:
    auto_ptr<AstExpression> base_;
    auto_ptr<AstExpression> index_;
};


class AstBinaryExpression : public AstExpression {
  public:
    AstBinaryExpression(FileLocation* location, AstExpression* left_side, 
                        BinaryOperator op, AstExpression* right_side);
    virtual ~AstBinaryExpression();

    virtual void Accept(Visitor& visitor) const;

    inline const AstExpression& left_side() const {
        return *left_side_;
    }

    inline BinaryOperator op() const {
        return op_;
    }

    inline const AstExpression& right_side() const {
        return *right_side_;
    }

  private:
    auto_ptr<AstExpression> left_side_;
    BinaryOperator op_;
    auto_ptr<AstExpression> right_side_;
};


class AstUnaryExpression : public AstExpression {
  public:
    AstUnaryExpression(FileLocation* location, AstExpression* expression, 
                       UnaryOperator op);
    virtual ~AstUnaryExpression();

    virtual void Accept(Visitor& visitor) const;

    inline const AstExpression& expression() const {
        return *expression_;
    }

    inline UnaryOperator op() const {
        return op_;
    }

  private:
    auto_ptr<AstExpression> expression_;
    UnaryOperator op_;
};


class AstCall : public AstExpression {
  public:
    AstCall(FileLocation* location, AstAccess* base, 
            vector<AstExpression*>* arguments);
    virtual ~AstCall();

    virtual void Accept(Visitor& visitor) const;

    inline const AstAccess& base() const {
        return *base_;
    }

    inline const vector<AstExpression*>& arguments() const {
        return *arguments_;
    }

  private:
    auto_ptr<AstAccess> base_;
    auto_collection_ptr<vector<AstExpression*> > arguments_;
};


class AstNew : public AstExpression {
  public:
    virtual ~AstNew();

  protected:
    AstNew(FileLocation* location);
};


class AstNewInstance : public AstNew {
  public:
    AstNewInstance(FileLocation* location, AstType* type, 
                   vector<AstExpression*>* arguments);
    virtual ~AstNewInstance();

    virtual void Accept(Visitor& visitor) const;

    inline const AstType& type() const {
        return *type_;
    }

    inline const vector<AstExpression*>& arguments() const {
        return *arguments_;
    }

  private:
    auto_ptr<AstType> type_;
    auto_collection_ptr<vector<AstExpression*> > arguments_;
};


class AstNewArray : public AstNew {
  public:
    AstNewArray(FileLocation* location, AstType* element_type, 
                AstExpression* size);
    virtual ~AstNewArray();

    virtual void Accept(Visitor& visitor) const;

    inline const AstType& element_type() const {
        return *element_type_;
    }

    inline const AstExpression& size() const {
        return *size_;
    }

  private:
    auto_ptr<AstType> element_type_;
    auto_ptr<AstExpression> size_;
};


class AstTypeTest : public AstExpression {
  public:
    AstTypeTest(FileLocation* location, AstExpression* expression, 
                AstType* type);
    virtual ~AstTypeTest();

    virtual void Accept(Visitor& visitor) const;

    inline const AstExpression& expression() const {
        return *expression_;
    }

    inline const AstType& type() const {
        return *type_;
    }

  private:
    auto_ptr<AstExpression> expression_;
    auto_ptr<AstType> type_;
};


class AstThis : public AstExpression {
  public:
    AstThis(FileLocation* location);
    virtual ~AstThis();

    virtual void Accept(Visitor& visitor) const;
};


class AstConstant : public AstExpression {
  public:
    virtual ~AstConstant();

  protected:
    AstConstant(FileLocation* location);
};


class AstIntegerConstant : public AstConstant {
  public:
    AstIntegerConstant(FileLocation* location, int value);
    virtual ~AstIntegerConstant();

    virtual void Accept(Visitor& visitor) const;

    inline int value() const {
        return value_;
    }

  private:
    int value_;
};


class AstStringConstant : public AstConstant {
  public:
    AstStringConstant(FileLocation* location, string* value);
    virtual ~AstStringConstant();

    virtual void Accept(Visitor& visitor) const;

    inline const string& value() const {
        return *value_;
    }

  private:
    auto_ptr<string> value_;
};


class AstFloatConstant : public AstConstant {
  public:
    AstFloatConstant(FileLocation* location, double value);
    virtual ~AstFloatConstant();

    virtual void Accept(Visitor& visitor) const;

    inline double value() const {
        return value_;
    }

  private:
    double value_;
};


class AstBooleanConstant : public AstConstant {
  public:
    AstBooleanConstant(FileLocation* location, bool value);
    virtual ~AstBooleanConstant();

    virtual void Accept(Visitor& visitor) const;

    inline bool value() const {
        return value_;
    }

  private:
    bool value_;
};


// Utility class for parsing class members
class ClassMembers {
  public:
    vector<AstMethod*>* methods;
    vector<AstVariable*>* variables;
};

} // namespace indie

#endif // INDIE_AST_H__
